//! bezier curve to polyline.
//! Example: data structure of bezier curve.
//! bezier_curve = [
//!     // Starting point of the cure
//!     [10, 10],
//!     [
//!     // BezierCurve segment data(controlPoint1, controlPoint2, endPoint)
//!         [20, 20], [40, 20], [50, 10]
//!     ],
//!     ...
//! ]

#![allow(dead_code)]

pub type Point = [f64; 2];

#[derive(Debug, Clone, Default)]
pub struct BezierCurve {
    pub start_point: Point,
    pub segments: Vec<[Point; 3]>,
}

impl BezierCurve {
    const DEFAULT_SEGMENT_POINTS_NUM: f64 = 50.;

    /// 贝塞尔曲线到一致的polyline数据
    pub fn bezier_curve_to_polyline(&self, precision: u8) -> Vec<[f64; 2]> {
        self.abstract_bazier_curve_to_polyline(precision).0
    }

    /// 获取贝塞尔曲线的长度
    pub fn get_bezier_curve_length(&self, precision: u8) -> f64 {
        let segment_points = self.abstract_bazier_curve_to_polyline(precision).0;
        let points_distance = &Self::get_segment_points_distance(&vec![segment_points])[0];
        Self::get_nums_sum(points_distance.iter().map(|v| *v))
    }

    // 抽象化贝塞尔曲线为一个polyline
    // precision: 计算精度，建议1-20，默认5比较好
    fn abstract_bazier_curve_to_polyline(&self, precision: u8) -> (Vec<[f64; 2]>, i32, i32) {
        let segments_num = self.segments.len();
        let start_point = self.start_point;
        let end_point = self.segments[segments_num - 1][2];
        let get_segment_t_point_funs = self
            .segments
            .iter()
            .enumerate()
            .map(|(i, s)| {
                let begin_point = if i == 0 {
                    start_point
                } else {
                    self.segments[i - 1][2]
                };

                Self::create_get_bezier_curve_t_point_fun(begin_point, s[0], s[1], s[2])
            })
            .collect::<Vec<_>>();
        // 初始化curve到polyline
        let segment_points_num = vec![Self::DEFAULT_SEGMENT_POINTS_NUM; segments_num];
        let segment_points =
            Self::get_segment_points_by_num(&get_segment_t_point_funs, &segment_points_num);

        // 迭代计算均匀分配的点
        let mut result = Self::calc_uniform_points_by_iteration(
            segment_points,
            &get_segment_t_point_funs,
            &self.segments,
            precision,
        );
        result.0.push(end_point);
        result
    }

    // 生成一个闭包
    fn create_get_bezier_curve_t_point_fun(
        begin_point: [f64; 2],
        control_point1: [f64; 2],
        control_point2: [f64; 2],
        endpoint: [f64; 2],
    ) -> impl Fn(f64) -> [f64; 2] {
        let r = move |t: f64| {
            let t_subed_1 = 1. - t;
            let t_subed_1_pow3 = t_subed_1.powi(3);
            let t_subed_1_pow2 = t_subed_1.powi(2);
            let t_pow3 = t.powi(3);
            let t_pow2 = t.powi(2);
            [
                begin_point[0] * t_subed_1_pow3
                    + 3. * control_point1[0] * t * t_subed_1_pow2
                    + 3. * control_point2[0] * t_pow2 * t_subed_1
                    + endpoint[0] * t_pow3,
                begin_point[1] * t_subed_1_pow3
                    + 3. * control_point1[1] * t * t_subed_1_pow2
                    + 3. * control_point2[1] * t_pow2 * t_subed_1
                    + endpoint[1] * t_pow3,
            ]
        };
        r
    }

    fn get_segment_points_by_num(
        funcs: &[impl Fn(f64) -> [f64; 2]],
        segments_points_num: &[f64],
    ) -> Vec<Vec<[f64; 2]>> {
        funcs
            .iter()
            .enumerate()
            .map(|(i, f)| {
                let t_gap: f64 = 1. / segments_points_num[i];
                (0..(segments_points_num[i] as usize))
                    .enumerate()
                    .map(|(i, _)| f(i as f64 * t_gap))
                    .collect::<Vec<_>>()
            })
            .collect::<Vec<_>>()
    }

    fn calc_uniform_points_by_iteration(
        mut segment_points: Vec<Vec<[f64; 2]>>,
        funcs: &[impl Fn(f64) -> [f64; 2]],
        segments: &[[[f64; 2]; 3]],
        precision: u8,
    ) -> (Vec<[f64; 2]>, i32, i32) {
        let mut rounds = 4;
        let mut cycles = 1;
        while rounds <= 1025 {
            let mut total_points_num = segment_points.iter().fold(0, |t, v| t + v.len());

            segment_points.iter_mut().enumerate().for_each(|(i, v)| {
                v.push(segments[i][2]);
            });

            let mut segment_points_distance = Self::get_segment_points_distance(&segment_points);

            let mut line_segment_num = segment_points_distance.iter().fold(0, |t, v| t + v.len());

            let mut segment_length = segment_points_distance
                .iter()
                .map(|v| Self::get_nums_sum(v.iter().map(|v| *v)))
                .collect::<Vec<_>>();

            let mut total_length = Self::get_nums_sum(segment_length.iter().map(|v| *v));

            let mut avg_length = total_length / line_segment_num as f64;

            // 检查精度
            let all_devications = Self::get_all_deviations(&segment_points_distance, avg_length);

            if all_devications <= precision as f64 {
                break;
            }

            total_points_num =
                (avg_length / precision as f64 * total_points_num as f64 * 1.1).ceil() as usize;

            let segment_points_num = segment_length
                .iter()
                .map(|len| (*len / total_length * total_points_num as f64).ceil())
                .collect::<Vec<_>>();

            // 重分布后计算点
            segment_points = Self::get_segment_points_by_num(&funcs, &segment_points_num);

            total_points_num = segment_points.iter().fold(0, |acc, v| acc + v.len());

            segment_points.iter_mut().enumerate().for_each(|(i, v)| {
                v.push(segments[i][2]);
            });

            segment_points_distance = Self::get_segment_points_distance(&segment_points);

            line_segment_num = segment_points_distance
                .iter()
                .fold(0, |acc, v| acc + v.len());

            segment_length = segment_points_distance
                .iter()
                .map(|v| Self::get_nums_sum(v.iter().map(|v| *v)))
                .collect::<Vec<_>>();

            total_length = Self::get_nums_sum(segment_length.iter().map(|v| *v));

            avg_length = total_length / line_segment_num as f64;

            let step_size = 1. / total_points_num as f64 / 10.;

            funcs.iter().enumerate().for_each(|(i, f)| {
                let current_segment_points_num = segment_points_num[i];
                let mut t = (0..current_segment_points_num as usize)
                    .enumerate()
                    .map(|(j, _)| j as f64 / segment_points_num[i])
                    .collect::<Vec<_>>();
                for _ in 0..rounds {
                    let distance = &Self::get_segment_points_distance(&segment_points)[0];
                    let deviations = distance.iter().map(|d| *d - avg_length).collect::<Vec<_>>();
                    let mut offset = 0f64;
                    for j in 1..(current_segment_points_num + 1.) as usize {
                        offset += deviations[j - 1];
                        t[j] -= step_size * offset;
                        if t[j] > 1.0 {
                            t[j] = 1.0;
                        }
                        if t[j] < 0. {
                            t[j] = 0.;
                        }
                        segment_points[i][j] = f(t[j]);
                    }
                }
            });

            rounds *= 4;

            cycles += 1;
        }

        let new_segment_points = segment_points.iter().fold(vec![], |mut acc, seg| {
            acc.extend_from_slice(seg);
            acc
        });
        (new_segment_points, cycles, rounds)
    }

    fn get_segment_points_distance(segment_points: &[Vec<[f64; 2]>]) -> Vec<Vec<f64>> {
        segment_points
            .iter()
            .map(|points| {
                (0..points.len() - 1)
                    .enumerate()
                    .map(|(i, _)| Self::get_two_point_distance(points[i], points[i + 1]))
                    .collect::<Vec<_>>()
            })
            .collect::<Vec<_>>()
    }

    fn get_two_point_distance(a: [f64; 2], b: [f64; 2]) -> f64 {
        ((a[0] - b[0]).powi(2) + (a[1] - b[1]).powi(2)).sqrt()
    }

    fn get_nums_sum<'a, T: Iterator<Item = f64>>(nums: T) -> f64 {
        nums.fold(0., |acc, n| acc + n)
    }

    fn get_all_deviations(segment_points_distance: &[Vec<f64>], avg_length: f64) -> f64 {
        segment_points_distance
            .iter()
            .map(|seg| seg.iter().map(|s| (*s - avg_length).abs()))
            .map(|seg| Self::get_nums_sum(seg))
            .fold(0., |total, v| total + v)
    }
}
